﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Text;
using System.Threading.Tasks;
using WubiTableTransformers;

namespace MicrosoftWubiTableInstaller
{
    internal sealed class Program
    {
        private const int FileNotFound = 1;
        private const int InvalidTableFile = 2;
        private const int SystemNotSupported = 3;
        private const int FailToCopyFile = 4;
        private const int ValidationFailed = 5;

        private const string ChsWubiDir = @"%SystemRoot%\InputMethod\CHS";
        private const string ChsWubiPath = ChsWubiDir + @"\ChsWubi.lex";
        private const string NewChsWubiPath = ChsWubiDir + @"\ChsWubiNew.lex";

        private readonly string[] args;
        private readonly string fileName;

        private readonly string chsWubiDir;
        private readonly string chsWubiPath;

        private string batchFilePath;
        private string aclFilePath;

        private Program(string[] args)
        {
            this.args = args;
            if (args.Length != 1)
                ShowHelp();
            else
            {
                this.fileName = args[0];
                this.chsWubiDir = Environment.ExpandEnvironmentVariables(ChsWubiDir);
                this.chsWubiPath = Environment.ExpandEnvironmentVariables(NewChsWubiPath);
                if (!File.Exists(chsWubiPath))
                    this.chsWubiPath = Environment.ExpandEnvironmentVariables(ChsWubiPath);
                CheckFile();
                CheckSystem();
                CopyFile();
                ValidateInstall();
            }
        }

        private void ShowHelp()
        {
            Console.Error.WriteLine("Usage:");
            Console.Error.WriteLine("  " + Environment.CommandLine + " FILE_NAME");
        }

        private void CheckFile()
        {
            if (!File.Exists(fileName))
            {
                Console.Error.WriteLine("Error: file not found");
                Environment.Exit(FileNotFound);
            }
            if (MicrosoftWubiTable.LoadBinaryTableFile(fileName) == null)
            {
                Console.Error.WriteLine("Error: invalid table file");
                Environment.Exit(InvalidTableFile);
            }
        }

        private void CheckSystem()
        {
            if (!File.Exists(chsWubiPath))
            {
                Console.Error.WriteLine("Error: system not supported");
                Environment.Exit(SystemNotSupported);
            }
        }

        private void CopyFile()
        {
            this.batchFilePath = GetTempFileName("CopyChsWubiToSystem", "bat");
            this.aclFilePath = GetTempFileName("SavedChsWubiACL", "dat");
            try
            {
                try
                {
                    GenerateBatchFile();
                    ExecuteBatchFile();
                }
                finally
                {
                    if (File.Exists(batchFilePath))
                        File.Delete(batchFilePath);
                    if (File.Exists(aclFilePath))
                        File.Delete(aclFilePath);
                }
            }
            catch
            {
                Console.Error.WriteLine("Error: fail to copy file");
                Environment.Exit(FailToCopyFile);
            }
        }

        private void ValidateInstall()
        {
            byte[] bytes1 = File.ReadAllBytes(fileName);
            byte[] bytes2 = File.ReadAllBytes(chsWubiPath);
            bool valid = true;
            if (bytes1.Length != bytes2.Length)
                valid = false;
            else
            {
                for (int i = 0; i < bytes1.Length; i++)
                    if (bytes1[i] != bytes2[i])
                        valid = false;
            }
            if (!valid)
            {
                Console.Error.WriteLine("Error: fail to validate install");
                Environment.Exit(ValidationFailed);
            }
        }

        private string GetTempFileName(string name, string extension)
        {
            string dir = Path.GetTempPath();
            Guid guid = Guid.NewGuid();
            string baseName = string.Format("{0}.{{{1}}}.{2}", name, guid, extension);
            return Path.Combine(dir, baseName);
        }

        private void GenerateBatchFile()
        {
            using (TextWriter writer = new StreamWriter(batchFilePath, false, Encoding.Default))
            {
                writer.WriteLine("@echo off");
                writer.WriteLine();
                writer.WriteLine("takeown /f \"{0}\"", chsWubiPath);
                writer.WriteLine("icacls \"{0}\" /save \"{1}\"", chsWubiPath, aclFilePath);
                writer.WriteLine("icacls \"{0}\" /grant Administrators:(F)", chsWubiPath);
                writer.WriteLine("copy \"{0}\" \"{1}\"", fileName, chsWubiPath);
                writer.WriteLine("icacls \"{0}\" /setowner \"NT SERVICE\\TrustedInstaller\"", chsWubiPath);
                writer.WriteLine("icacls \"{0}\" /restore \"{1}\"", chsWubiDir, aclFilePath);
                writer.WriteLine("rem pause");
            }
        }

        private void ExecuteBatchFile()
        {
            Process process = new Process();
            process.StartInfo.FileName = batchFilePath;
            process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            process.Start();
            process.WaitForExit();
        }

        static void Main(string[] args)
        {
            new Program(args);
        }
    }
}
